#region License

// This file is part of Grawlix.
// 
// Grawlix is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// Grawlix is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with Grawlix.  If not, see
// <http://www.gnu.org/licenses/>.

#endregion

// Original copyright:

/*
  Box2DX Copyright (c) 2008 Ihar Kalasouski http://code.google.com/p/box2dx
  Box2D original C++ version Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

using System;
using System.Diagnostics.CodeAnalysis;

namespace Grawlix.Geometry
{
	/// <summary>
	/// A general-purpose 2x2 matrix structure,
	/// specified in column-major order.
	/// </summary>
	public struct Matrix22 : IEquatable<Matrix22>, IFormattable
	{
		/// <summary>
		/// The identity matrix.
		/// </summary>
		public static readonly Matrix22 Identity = new Matrix22(
			1, 0, 0, 1);

		/// <summary>
		/// The zero matrix.
		/// </summary>
		public static readonly Matrix22 Zero = new Matrix22(0, 0,
		                                                    0, 0);

		/// <summary>
		/// The first column vector.
		/// </summary>
		private readonly Vector2 _x;

		/// <summary>
		/// The second column vector.
		/// </summary>
		private readonly Vector2 _y;

		/// <summary>
		/// Initializes this <see cref="Matrix22"/> structure from
		/// a given set of scalars in column-major order.
		/// </summary>
		/// <param name="scalar11">The top-left value.</param>
		/// <param name="scalar12">The bottom-left value.</param>
		/// <param name="scalar21">The top-right value.</param>
		/// <param name="scalar22">The bottom-right value.</param>
		[SuppressMessage("Microsoft.Design", "CA1025",
			Justification =
				"There should only be a fixed number of parameters.")]
		public Matrix22(float scalar11, float scalar12,
		                float scalar21, float scalar22)
			: this(
				new Vector2(scalar11, scalar12),
				new Vector2(scalar21, scalar22))
		{
		}

		/// <summary>
		/// Initializes this <see cref="Matrix22"/> structure from
		/// given column vectors.
		/// </summary>
		/// <param name="column1">The first column vector.</param>
		/// <param name="column2">The second column vector.</param>
		public Matrix22(Vector2 column1, Vector2 column2)
		{
			_x = column1;
			_y = column2;
		}

		/// <summary>
		/// Finds the determinant of the matrix.
		/// </summary>
		public float Determinant
		{
			get { return X.X * Y.Y - Y.X * X.Y; }
		}

		/// <summary>
		/// The first column vector.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704",
			Justification =
				"X and Y are the best I could come up with " +
				"for the column vectors.")]
		public Vector2 X
		{
			get { return _x; }
		}

		/// <summary>
		/// The second column vector.
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704",
			Justification =
				"X and Y are the best I could come up with " +
				"for the column vectors.")]
		public Vector2 Y
		{
			get { return _y; }
		}

		/// <summary>
		/// Computes the sum of two matrices.
		/// </summary>
		/// <param name="rhs">The right-hand addend.</param>
		/// <returns>The sum of the two matrices.</returns>
		public Matrix22 Add(Matrix22 rhs)
		{
			return this + rhs;
		}

		/// <summary>
		/// Computes the product of a matrix and the reciprocal of a scalar.
		/// </summary>
		/// <param name="scalar">The reciprocal of the scalar.</param>
		/// <returns>The product of the matrix and scalar reciprocal.</returns>
		public Matrix22 Divide(float scalar)
		{
			return this / scalar;
		}

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <returns>
		/// true if <paramref name="obj" /> and this
		/// instance are the same type and represent
		/// the same value; otherwise, false.
		/// </returns>
		/// <param name="obj">Another object to compare to.</param>
		public override bool Equals(object obj)
		{
			return obj.GetType() == typeof(Matrix22) &&
			       Equals((Matrix22)obj);
		}

		/// <summary>
		/// Indicates whether the current object is equal to another
		/// object of the same type.
		/// </summary>
		/// <returns>
		/// true if the current object is equal to the
		/// <paramref name="other" /> parameter; otherwise, false.
		/// </returns>
		/// <param name="other">An object to compare with this object.
		/// </param>
		public bool Equals(Matrix22 other)
		{
			return other._x.Equals(_x) && other._y.Equals(_y);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		public override int GetHashCode()
		{
			unchecked
			{
				return (_x.GetHashCode() * 397) ^ _y.GetHashCode();
			}
		}

		/// <summary>
		/// Compute the inverse of this matrix.
		/// </summary>
		/// <returns>The inverse of the matrix.</returns>
		/// <remarks>Multiplying by an inverse matrix
		/// yields the identity matrix.
		/// (This calculation may be slightly off due to inaccuracies
		/// in the floating point representation.)</remarks>
		/// <exception cref="SingularMatrixException">
		/// The matrix is singular (cannot be inverted).</exception>
		public Matrix22 Invert()
		{
			float det = Determinant;
			if (det != 0.0f)
				return new Matrix22(Y.Y, -X.Y, -Y.X, X.X) / det;
			throw new SingularMatrixException(
				"Attempt to invert a non-invertible 2x2 matrix " +
				"with determinant " + det + ".\n" + this);
		}

		/// <summary>
		/// Computes the inner product of two matrices.
		/// </summary>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the two matrices.</returns>
		public Matrix22 Multiply(Matrix22 rhs)
		{
			return this * rhs;
		}

		/// <summary>
		/// Computes the inner product of a matrix and vector.
		/// </summary>
		/// <param name="vector">The vector to multiply with.</param>
		/// <returns>The inner product of the matrix and vector.</returns>
		public Vector2 Multiply(Vector2 vector)
		{
			return this * vector;
		}

		/// <summary>
		/// Computes the product of a matrix and scalar.
		/// </summary>
		/// <param name="scalar">The scalar value.</param>
		/// <returns>The product of the matrix and scalar.</returns>
		public Matrix22 Multiply(float scalar)
		{
			return this * scalar;
		}

		/// <summary>
		/// Computes the inner product of the matrix transposed and a vector.
		/// </summary>
		/// <param name="vector">The vector to multiply with.</param>
		/// <returns>The inner product of the matrix transposed and
		/// the vector.</returns>
		public Vector2 MultiplyTranspose(Vector2 vector)
		{
			return new Vector2(_x.X * vector.X + _x.Y * vector.Y,
			                   _y.X * vector.X + _y.Y * vector.Y);
		}

		/// <summary>
		/// Computes the negation of a matrix.
		/// </summary>
		/// <returns>The negated matrix.</returns>
		public Matrix22 Negate()
		{
			return -this;
		}

		/// <summary>
		/// Solve A * x = result, for x, where result is a column vector.
		/// </summary>
		/// <param name="result">The result of multiplying this
		/// <see cref="Matrix22"/> by the return value.</param>
		/// <returns>The solved-for vector.</returns>
		/// <remarks>
		/// Computing the inverse is a more general way to solve this problem,
		/// but this direct solution can be computed more efficiently.
		/// </remarks>
		/// <exception cref="SingularMatrixException">
		/// The matrix is singular (cannot be inverted).</exception>
		public Vector2 Solve(Vector2 result)
		{
			float det = Determinant;
			if (det != 0.0f)
			{
				float x = Y.Y * result.X - Y.X * result.Y;
				float y = X.X * result.Y - X.Y * result.X;
				return new Vector2(x, y) / det;
			}
			throw new SingularMatrixException(
				"Attempt to solve against a non-invertible 2x2 matrix " +
				"with determinant " + det + ".\n" + this);
		}

		/// <summary>
		/// Computes the difference between two matrices.
		/// </summary>
		/// <param name="rhs">The subtrahend.</param>
		/// <returns>The difference of the two matrices.</returns>
		public Matrix22 Subtract(Matrix22 rhs)
		{
			return this - rhs;
		}

		/// <summary>
		/// Obtains a string representation of this object.
		/// </summary>
		/// <returns>A string representation of this object.</returns>
		public override string ToString()
		{
			return ToString(null, null);
		}

		/// <summary>
		/// Formats the value of the current instance using numbers in
		/// the specified format.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String" /> containing the value of
		/// the current instance in the specified format.
		/// </returns>
		/// <param name="format">The <see cref="T:System.String" />
		/// specifying the number format to use, or null to specify the
		/// default.
		/// </param>
		/// <param name="formatProvider">The
		/// <see cref="T:System.IFormatProvider" /> to use to format
		/// the value, or null to obtain the numeric format information
		/// from the current locale setting of the operating system. 
		/// </param>
		public string ToString(string format,
		                       IFormatProvider formatProvider)
		{
			return "[" + X.ToString(format, formatProvider) + "; " +
			       Y.ToString(format, formatProvider) + "]";
		}

		/// <summary>
		/// Compute the transpose of this matrix.
		/// </summary>
		/// <returns>The transpose of the matrix.</returns>
		public Matrix22 Transpose()
		{
			return new Matrix22(_x.X, _y.X, _x.Y, _y.Y);
		}

		/// <summary>
		/// Computes the sum of two matrices.
		/// </summary>
		/// <param name="lhs">The left-hand addend.</param>
		/// <param name="rhs">The right-hand addend.</param>
		/// <returns>The sum of the two matrices.</returns>
		public static Matrix22 operator +(
			Matrix22 lhs, Matrix22 rhs)
		{
			return new Matrix22(lhs.X + rhs.X, lhs.Y + rhs.Y);
		}

		/// <summary>
		/// Computes the product of a matrix and the reciprocal of a scalar.
		/// </summary>
		/// <param name="matrix">The matrix to scale.</param>
		/// <param name="scalar">The reciprocal of the scalar.</param>
		/// <returns>The product of the matrix and scalar reciprocal.</returns>
		public static Matrix22 operator /(
			Matrix22 matrix, float scalar)
		{
			return new Matrix22(matrix.X / scalar, matrix.Y / scalar);
		}

		/// <summary>
		/// Determines whether two <see cref="Matrix22"/> objects
		/// have the same value.
		/// </summary>
		/// <param name="left">A <see cref="Matrix22"/>.</param>
		/// <param name="right">A <see cref="Matrix22"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is the
		/// same as <paramref name="right"/>; else false.</returns>
		public static bool operator ==(
			Matrix22 left, Matrix22 right)
		{
			return left.Equals(right);
		}

		/// <summary>
		/// Determines whether two <see cref="Matrix22"/> objects
		/// have different values.
		/// </summary>
		/// <param name="left">A <see cref="Matrix22"/>.</param>
		/// <param name="right">A <see cref="Matrix22"/>.</param>
		/// <returns>true if the value of <paramref name="left"/> is different
		/// from <paramref name="right"/>; else false.</returns>
		public static bool operator !=(
			Matrix22 left, Matrix22 right)
		{
			return !left.Equals(right);
		}

		/// <summary>
		/// Computes the inner product of two matrices.
		/// </summary>
		/// <param name="lhs">The multiplicand.</param>
		/// <param name="rhs">The multiplier.</param>
		/// <returns>The inner product of the two matrices.</returns>
		public static Matrix22 operator *(
			Matrix22 lhs, Matrix22 rhs)
		{
			return new Matrix22(
				lhs.X.X * rhs.X.X + lhs.Y.X * rhs.X.Y,
				lhs.X.Y * rhs.X.X + lhs.Y.Y * rhs.X.Y,
				lhs.X.X * rhs.Y.X + lhs.Y.X * rhs.Y.Y,
				lhs.X.Y * rhs.Y.X + lhs.Y.Y * rhs.Y.Y);
		}

		/// <summary>
		/// Computes the inner product of a matrix and vector.
		/// </summary>
		/// <param name="matrix">The matrix to multiply.</param>
		/// <param name="vector">The vector to multiply with.</param>
		/// <returns>The inner product of the matrix and vector.
		/// </returns>
		public static Vector2 operator *(
			Matrix22 matrix, Vector2 vector)
		{
			return
				new Vector2(
					matrix.X.X * vector.X + matrix.Y.X * vector.Y,
					matrix.X.Y * vector.X + matrix.Y.Y * vector.Y);
		}

		/// <summary>
		/// Computes the product of a matrix and scalar.
		/// </summary>
		/// <param name="matrix">The matrix to scale.</param>
		/// <param name="scalar">The scalar value.</param>
		/// <returns>The product of the matrix and scalar.</returns>
		public static Matrix22 operator *(
			Matrix22 matrix, float scalar)
		{
			return new Matrix22(matrix.X * scalar, matrix.Y * scalar);
		}

		/// <summary>
		/// Computes the product of a matrix and scalar.
		/// </summary>
		/// <param name="matrix">The matrix to scale.</param>
		/// <param name="scalar">The scalar value.</param>
		/// <returns>The product of the matrix and scalar.</returns>
		public static Matrix22 operator *(
			float scalar, Matrix22 matrix)
		{
			return matrix * scalar;
		}

		/// <summary>
		/// Computes the difference between two matrices.
		/// </summary>
		/// <param name="lhs">The minuend.</param>
		/// <param name="rhs">The subtrahend.</param>
		/// <returns>The difference of the two matrices.</returns>
		public static Matrix22 operator -(
			Matrix22 lhs, Matrix22 rhs)
		{
			return new Matrix22(lhs.X - rhs.X, lhs.Y - rhs.Y);
		}

		/// <summary>
		/// Computes the negation of a matrix.
		/// </summary>
		/// <param name="lhs">The matrix to negate.</param>
		/// <returns>The negated matrix.</returns>
		public static Matrix22 operator -(Matrix22 lhs)
		{
			return new Matrix22(-lhs.X, -lhs.Y);
		}
	}
}